package com.example.boot3.rpc;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Map;
import java.util.regex.Pattern;

/**
 * Custom formula calculator
 * Supports formulas based on fields and arithmetic operations (+, -, *, /)
 */
public class FormulaCalculator {
    
    // Pattern to match numbers or field names
    private static final Pattern NUMBER_OR_FIELD_PATTERN = Pattern.compile("([a-zA-Z_][a-zA-Z0-9_]*|\\d+(\\.\\d+)?)");
    // Pattern to match operators
    private static final Pattern OPERATOR_PATTERN = Pattern.compile("[+\\-*/]");
    
    /**
     * Calculate formula result
     * @param formula Formula string, e.g. "countNum * price"
     * @param fieldValues Field values map
     * @return Calculation result with 2 decimal places
     */
    public static BigDecimal calculate(String formula, Map<String, BigDecimal> fieldValues) {
        // Remove spaces
        formula = formula.replaceAll("\\s+", "");
        
        // Simple formula parsing and calculation
        return evaluateExpression(formula, fieldValues);
    }
    
    /**
     * Parse and evaluate expression
     * @param expression Expression to evaluate
     * @param fieldValues Field values
     * @return Calculation result
     */
    private static BigDecimal evaluateExpression(String expression, Map<String, BigDecimal> fieldValues) {
        try {
            // Use two-stack method to calculate expression (operand stack and operator stack)
            java.util.Stack<BigDecimal> numbers = new java.util.Stack<>();
            java.util.Stack<Character> operators = new java.util.Stack<>();
            
            for (int i = 0; i < expression.length(); i++) {
                char ch = expression.charAt(i);
                
                if (Character.isLetterOrDigit(ch) || ch == '.') {
                    // Parse number or field name
                    StringBuilder sb = new StringBuilder();
                    while (i < expression.length() && 
                           (Character.isLetterOrDigit(expression.charAt(i)) || expression.charAt(i) == '.')) {
                        sb.append(expression.charAt(i));
                        i++;
                    }
                    i--; // Step back one position
                    
                    String token = sb.toString();
                    BigDecimal value;
                    if (fieldValues.containsKey(token)) {
                        value = fieldValues.get(token);
                    } else {
                        try {
                            value = new BigDecimal(token);
                        } catch (NumberFormatException e) {
                            throw new IllegalArgumentException("Unrecognized field or number: " + token);
                        }
                    }
                    numbers.push(value);
                } else if (ch == '(') {
                    operators.push(ch);
                } else if (ch == ')') {
                    while (!operators.isEmpty() && operators.peek() != '(') {
                        processOperator(numbers, operators);
                    }
                    if (!operators.isEmpty()) {
                        operators.pop(); // Remove '('
                    }
                } else if (isOperator(ch)) {
                    while (!operators.isEmpty() && precedence(operators.peek()) >= precedence(ch) 
                           && operators.peek() != '(') {
                        processOperator(numbers, operators);
                    }
                    operators.push(ch);
                }
            }
            
            while (!operators.isEmpty()) {
                processOperator(numbers, operators);
            }
            
            return numbers.pop().setScale(2, RoundingMode.HALF_UP);
        } catch (Exception e) {
            throw new IllegalArgumentException("Formula calculation error: " + e.getMessage(), e);
        }
    }
    
    /**
     * Process operator
     */
    private static void processOperator(java.util.Stack<BigDecimal> numbers, java.util.Stack<Character> operators) {
        if (numbers.size() < 2 || operators.isEmpty()) {
            throw new IllegalArgumentException("Invalid formula format");
        }
        
        BigDecimal b = numbers.pop();
        BigDecimal a = numbers.pop();
        char op = operators.pop();
        
        BigDecimal result;
        switch (op) {
            case '+':
                result = a.add(b);
                break;
            case '-':
                result = a.subtract(b);
                break;
            case '*':
                result = a.multiply(b);
                break;
            case '/':
                if (b.compareTo(BigDecimal.ZERO) == 0) {
                    throw new ArithmeticException("Division by zero");
                }
                result = a.divide(b, 10, RoundingMode.HALF_UP); // Keep more precision for intermediate calculations
                break;
            default:
                throw new IllegalArgumentException("Unsupported operator: " + op);
        }
        
        numbers.push(result);
    }
    
    /**
     * Check if character is an operator
     */
    private static boolean isOperator(char ch) {
        return ch == '+' || ch == '-' || ch == '*' || ch == '/';
    }
    
    /**
     * Get operator precedence
     */
    private static int precedence(char op) {
        switch (op) {
            case '+':
            case '-':
                return 1;
            case '*':
            case '/':
                return 2;
            default:
                return 0;
        }
    }
}